package com.puff.framework.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Properties;
import java.util.concurrent.ConcurrentMap;

import com.puff.core.Puff;
import com.puff.framework.utils.jdk.ConcurrentHashMapV8;

public class PropUtil {

	private static Prop prop = null;
	private static final ConcurrentMap<String, Prop> map = new ConcurrentHashMapV8<String, Prop>();

	private PropUtil() {
	}

	public static Prop use(String fileName) {
		return use(fileName, Puff.getEncoding());
	}

	public static Prop use(String fileName, String encoding) {
		Prop result = map.get(fileName);
		if (result == null) {
			result = new Prop(fileName, encoding);
			map.put(fileName, result);
			if (PropUtil.prop == null)
				PropUtil.prop = result;
		}
		return result;
	}

	/**
	 * Using the properties file bye File object. It will loading the properties file if not loading.
	 * @see #use(File, String)
	 */
	public static Prop use(File file) {
		return use(file, Puff.getEncoding());
	}

	/**
	 * Using the properties file bye File object. It will loading the properties file if not loading.
	 * <p>
	 * Example:<br>
	 * PropUtil.use(new File("/var/config/my_config.txt"), "UTF-8");<br>
	 * Strig userName = PropUtil.use("my_config.txt").get("userName");
	 * 
	 * @param file the properties File object
	 * @param encoding the encoding
	 */
	public static Prop use(File file, String encoding) {
		Prop result = map.get(file.getName());
		if (result == null) {
			result = new Prop(file, encoding);
			map.put(file.getName(), result);
			if (PropUtil.prop == null)
				PropUtil.prop = result;
		}
		return result;
	}

	public static Prop useless(String fileName) {
		Prop previous = map.remove(fileName);
		if (PropUtil.prop == previous)
			PropUtil.prop = null;
		return previous;
	}

	public static void clear() {
		prop = null;
		map.clear();
	}

	public static Prop getProp() {
		if (prop == null)
			throw new IllegalStateException("Load propties file by invoking PropUtil.use(String fileName) method first.");
		return prop;
	}

	public static Prop getProp(String fileName) {
		return map.get(fileName);
	}

	public static String get(String key) {
		return getProp().get(key);
	}

	public static String get(String key, String defaultValue) {
		return getProp().get(key, defaultValue);
	}

	public static Integer getInt(String key) {
		return getProp().getInt(key);
	}

	public static Integer getInt(String key, Integer defaultValue) {
		return getProp().getInt(key, defaultValue);
	}

	public static Long getLong(String key) {
		return getProp().getLong(key);
	}

	public static Long getLong(String key, Long defaultValue) {
		return getProp().getLong(key, defaultValue);
	}

	public static Boolean getBoolean(String key) {
		return getProp().getBoolean(key);
	}

	public static Boolean getBoolean(String key, Boolean defaultValue) {
		return getProp().getBoolean(key, defaultValue);
	}

	public static boolean containsKey(String key) {
		return getProp().containsKey(key);
	}

	private static class Prop {

		private Properties properties = null;

		/**
		 * Prop constructor.
		 * @see #Prop(String, String)
		 */
		private Prop(String fileName) {
			this(fileName, Puff.getEncoding());
		}

		private Prop(String fileName, String encoding) {
			InputStream inputStream = null;
			try {
				inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName); // properties.load(Prop.class.getResourceAsStream(fileName));
				if (inputStream == null)
					throw new IllegalArgumentException("Properties file not found in classpath: " + fileName);
				properties = new Properties();
				properties.load(new InputStreamReader(inputStream, encoding));
			} catch (IOException e) {
				throw new RuntimeException("Error loading properties file.", e);
			} finally {
				if (inputStream != null)
					try {
						inputStream.close();
					} catch (IOException e) {
					}
			}
		}

		/**
		 * Prop constructor.
		 * @see #Prop(File, String)
		 */
		private Prop(File file) {
			this(file, Puff.getEncoding());
		}

		/**
		 * Prop constructor
		 * <p>
		 * Example:<br>
		 * Prop prop = new Prop(new File("/var/config/my_config.txt"), "UTF-8");<br>
		 * String userName = prop.get("userName");
		 * 
		 * @param file the properties File object
		 * @param encoding the encoding
		 */
		private Prop(File file, String encoding) {
			if (file == null)
				throw new IllegalArgumentException("File can not be null.");
			if (file.isFile() == false)
				throw new IllegalArgumentException("File not found : " + file.getName());

			InputStream inputStream = null;
			try {
				inputStream = new FileInputStream(file);
				properties = new Properties();
				properties.load(new InputStreamReader(inputStream, encoding));
			} catch (IOException e) {
				throw new RuntimeException("Error loading properties file.", e);
			} finally {
				if (inputStream != null)
					try {
						inputStream.close();
					} catch (IOException e) {
					}
			}
		}

		public String get(String key) {
			return properties.getProperty(key);
		}

		public String get(String key, String defaultValue) {
			return properties.getProperty(key, defaultValue);
		}

		public Integer getInt(String key) {
			return getInt(key, null);
		}

		public Integer getInt(String key, Integer defaultValue) {
			String value = properties.getProperty(key);
			if (value != null)
				return Integer.parseInt(value.trim());
			return defaultValue;
		}

		public Long getLong(String key) {
			return getLong(key, null);
		}

		public Long getLong(String key, Long defaultValue) {
			String value = properties.getProperty(key);
			if (value != null)
				return Long.parseLong(value.trim());
			return defaultValue;
		}

		public Boolean getBoolean(String key) {
			return getBoolean(key, null);
		}

		public Boolean getBoolean(String key, Boolean defaultValue) {
			String value = properties.getProperty(key);
			if (value != null) {
				value = value.toLowerCase().trim();
				if ("true".equals(value))
					return true;
				else if ("false".equals(value))
					return false;
				throw new RuntimeException("The value can not parse to Boolean : " + value);
			}
			return defaultValue;
		}

		public boolean containsKey(String key) {
			return properties.containsKey(key);
		}

	}
}